package recovery

import (
	"context"
	"fmt"
	"runtime"
	"strings"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/middleware"
)

// ErrUnknownRequest is unknown request error.
var ErrUnknownRequest = errors.InternalServer("PANIC", "服务器内部错误")

// HandlerFunc is recovery handler func.
type HandlerFunc func(ctx context.Context, req, err interface{}) error

// Option is recovery option.
type Option func(*options)

type options struct {
	handler HandlerFunc
	logger  log.Logger
}

// WithHandler with recovery handler.
func WithHandler(h HandlerFunc) Option {
	return func(o *options) {
		o.handler = h
	}
}

// WithLogger with recovery logger.
func WithLogger(logger log.Logger) Option {
	return func(o *options) {
		o.logger = logger
	}
}

// Recovery is a server middleware that recovers from any panics.
func Recovery(opts ...Option) middleware.Middleware {
	op := options{
		logger: log.DefaultLogger,
		handler: func(ctx context.Context, req, err interface{}) error {
			return ErrUnknownRequest
		},
	}
	for _, o := range opts {
		o(&op)
	}
	logger := log.NewHelper(op.logger)
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
			defer func() {
				if rerr := recover(); rerr != nil {
					by := strings.Builder{}
					by.WriteString(fmt.Sprintf("%v\n", rerr))
					pc := make([]uintptr, 15)
					n := runtime.Callers(2, pc)
					pc = pc[:n]
					frm := runtime.CallersFrames(pc)
					for f, _ := frm.Next(); f.File != ""; f, _ = frm.Next() {
						if strings.Contains(f.File, "kratos/middleware/logging/logging.go") {
							break
						}
						fname := f.Func.Name()
						last := strings.Index(fname, "/")
						by.WriteString(fmt.Sprintf("%s:%d:%s\n", f.File, f.Line, fname[last+1:]))
					}
					logger.Error(by.String())
					err = op.handler(ctx, req, rerr)
				}
			}()
			return handler(ctx, req)
		}
	}
}
